home *** CD-ROM | disk | FTP | other *** search
- /*
- * COMMANDS.C
- *
- * This module contains all the command parsing stuff used to parse the
- * commands in the script file.
- *
- */
-
- #ifndef LATTICE_50
- #include "system.h"
- #endif
-
- #include "bbsindex.h"
-
- /*
- * The list of command names and associated functions
- */
-
- void com_append(), com_close(), com_config(), com_database(), com_echo(),
- com_exec(), com_format(), com_list(), com_macro(), com_msg(),
- com_open(), com_reset(), com_scan(), com_trace(), com_ignore();
-
- struct {
- char *name;
- void (*proc)();
- } com[] = {
-
- { "APPEND", com_append },
- { "CHECKFILES", com_checkfiles },
- { "CLOSE", com_close },
- { "CONFIG", com_config },
- { "DATABASE", com_database },
- { "ECHO", com_echo },
- { "EXEC", com_exec },
- { "FOREIGN", com_foreign },
- { "FORMAT", com_format },
- { "IGNORE", com_ignore },
- { "LIST", com_list },
- { "MACRO", com_macro },
- { "MSG", com_msg },
- { "NOREQUEST", com_norequest },
- { "OPEN", com_open },
- { "RESET", com_reset },
- { "SCAN", com_scan },
- { "SELECT", com_select },
- { "SORT", com_sort },
- { "TRACE", com_trace },
- { NULL, NULL }
- };
-
- struct constval {
- struct constval *next;
- char *name;
- char *value;
- };
- typedef struct constval CONST;
-
- CONST *firstconst = NULL; /* Pointer to first constant on list */
-
- static char line[MAXCOM]; /* Temporary line buffer */
-
- /*
- * scripterror()
- * -------------
- * Prints an error message for the current script command; the filename
- * and linenumber are automatically output, followed by the user
- * specified message. The message should be terminated with a NL,
- * unless the caller intends outputting any more info afterwards.
- */
- void scripterror(s)
- char *s;
- {
- print3("===> ", scriptname, " (");
- print3(itoa(linenum), "): ",s);
- }
-
- /*
- * addconst()
- * --------
- * Creates a new constant entry, and links it into the list of
- * existing constant definitions. The name of the constant is
- * initialised appropriately. A pointer to the constant is returned.
- */
- CONST *addconst(name)
- char *name;
- {
- CONST *tv;
-
- tv = mymalloc(sizeof(CONST));
- tv->next = firstconst;
- firstconst = tv;
- tv->name = mymalloc(strlen(name)+1);
- strcpy(tv->name, name);
- return (tv);
- }
-
- /*
- * findconst()
- * ---------
- * Searches constant table for specified constant, and returns a
- * pointer to it if found, or NULL if not found.
- */
- CONST *findconst(name)
- char *name;
- {
- CONST *tv;
-
- for (tv = firstconst; tv && stricmp(name, tv->name); tv = tv->next)
- ;
- return (tv);
- }
-
-
- /*
- * readcommand()
- * -------------
- * This function reads a command from the script buffer into a
- * specified command buffer. The following modifications are made
- * to the original script text:
- *
- * - Anything after a # to an end of line is ignored
- * - Any line ending with \ is continued on the next line
- * - Everything outside quotes is capitalised.
- * - Any white space outside quotes gets reduced to a single space
- * - White space surrounding command lines is removed.
- * - Any commas on the line are removed, and replaced by spaces
- *
- * The script command is terminated by either a semicolon or a newline.
- * The command buffer is null-terminated on return. When the end of the
- * script is reached, a 0 is returned. The maximum size of the command
- * line is specified in max - if this is exceeded, then an error message
- * is generated. Normally, the length of the command line read in is
- * returned.
- *
- * For the technically minded, a mini state machine is setup, to handle
- * the different requirements.
- */
-
- #define STATE_START 1 /* Skip over space at start of command */
- #define STATE_SPACE 2 /* Replace multiple white space by single space */
- #define STATE_COPY 3 /* Copy normal text, capitalising */
- #define STATE_IGNORE 4 /* If next char is newline, then skip it */
- #define STATE_QUOTE 5 /* Copy text up until next quote */
- #define STATE_IGQUOTE 6 /* Like STATE_IGNORE, but between quotes */
- #define STATE_COMMENT 7 /* Ignore everything until the next newline */
- #define STATE_CONST_ST 8 /* Starting to expand a constant $(..) */
- #define STATE_CONST_CP 9 /* Copying constant name, and expanding it */
-
- /*
- * Retrieve next character from script buffer, updating line counter
- * and checking for end of buffer.
- */
- #define nextchar(ch) \
- ( (ch) = script[scriptpos++], \
- ((ch) == CHAR_NL ? linenum++ : 0), \
- ((scriptpos > scriptsize) ? (loop = 0) : 0) \
- )
-
- /*
- * Add character to command line, checking for end of buffer and
- * aborting if the buffer is overrun.
- */
- #define addchar(ch) \
- ( buf[pos++] = (ch), \
- ((pos > max) ? (scripterror("line too long\n"), Cleanup(10)) : 0) \
- )
-
- int readcommand(buf,max)
- char *buf;
- int max;
- {
- int pos = 0;
- int state = STATE_START;
- int laststate;
- int loop = 1;
- int ch;
- CONST *var;
- char varname[MAXCONST], *p;
- char openquote;
- int varpos;
-
- if (scriptpos >= scriptsize)
- return 0;
-
- nextchar(ch);
- while (loop) {
- switch (state) {
-
- case STATE_START:
- switch (ch) {
-
- case CHAR_SPACE:
- case CHAR_TAB:
- case CHAR_SEMI:
- case CHAR_NL:
- case CHAR_COMMA:
- nextchar(ch);
- break;
-
- case CHAR_HASH:
- state = STATE_COMMENT;
- break;
-
- default:
- state = STATE_COPY;
- break;
- }
- break;
-
- case STATE_SPACE:
- switch (ch) {
-
- case CHAR_SPACE:
- case CHAR_TAB:
- case CHAR_COMMA:
- nextchar(ch);
- break;
-
- case CHAR_NL:
- linenum--;
- /* Drop through */
- case CHAR_SEMI:
- scriptpos--;
- loop = 0;
- break;
-
- default:
- addchar(CHAR_SPACE);
- state = STATE_COPY;
- break;
- }
- break;
-
- case STATE_COPY:
- switch (ch) {
-
- /*
- * If we get a # after a command, then we immediately
- * stop, so that the comment will get eaten the NEXT
- * time we call readcommand(). If we went into
- * STATE_COMMENT, then the following command would end
- * up getting read into the buffer as well.
- *
- */
- case CHAR_HASH:
- scriptpos--;
- loop = 0;
- break;
-
- case CHAR_QUOTE:
- case CHAR_QUOTES:
- addchar(ch);
- openquote = ch;
- state = STATE_QUOTE;
- nextchar(ch);
- break;
-
- case CHAR_DOLLAR:
- laststate = state; /* Save return state */
- state = STATE_CONST_ST;
- nextchar(ch);
- break;
-
- case CHAR_NL:
- linenum--;
- /* Drop through */
-
- case CHAR_SEMI:
- loop = 0;
- scriptpos--;
- break;
-
- case CHAR_ESC:
- nextchar(ch);
- state = STATE_IGNORE;
- break;
-
- case CHAR_SPACE:
- case CHAR_TAB:
- case CHAR_COMMA:
- state = STATE_SPACE;
- break;
-
- default:
- addchar(toupper(ch));
- nextchar(ch);
- break;
- }
- break;
-
- case STATE_IGNORE:
- if (ch == CHAR_NL)
- ch = CHAR_SPACE;
- addchar(ch);
- nextchar(ch);
- state = STATE_COPY;
- break;
-
- case STATE_QUOTE:
- switch (ch) {
-
- case CHAR_NL:
- scriptpos--;
- linenum--;
- loop = 0;
- break;
-
- case CHAR_ESC:
- state = STATE_IGQUOTE;
- nextchar(ch);
- break;
-
- case CHAR_DOLLAR:
- laststate = state;
- state = STATE_CONST_ST;
- nextchar(ch);
- break;
-
- case CHAR_QUOTE:
- case CHAR_QUOTES:
- if (openquote == ch)
- state = STATE_COPY;
- /* Deliberate fall through to next switch */
-
- default:
- addchar(ch);
- nextchar(ch);
- break;
- }
- break;
-
- case STATE_IGQUOTE:
- if (ch != CHAR_NL) {
- addchar(CHAR_ESC);
- addchar(ch);
- }
- nextchar(ch);
- state = STATE_QUOTE;
- break;
-
- case STATE_COMMENT:
- if (ch == CHAR_NL)
- state = STATE_START;
- nextchar(ch);
- break;
-
- case STATE_CONST_ST:
- if (ch != '(') { /* If not a constant usage, copy unchanged */
- addchar(CHAR_DOLLAR);
- state = laststate;
- break;
- }
- varpos = 0;
- nextchar(ch);
- state = STATE_CONST_CP;
- break;
-
- case STATE_CONST_CP:
- if (ch == ')') {
- /*
- * Variable name has been fully entered, so now
- * expand it.
- */
- varname[varpos] = CHAR_NULL;
- var = findconst(varname);
- if (!var) {
- scripterror("unknown constant ");
- print2(varname, ".\n");
- Cleanup(10);
- }
- for (p = var->value; *p; p++)
- addchar(*p);
- state = laststate;
- } else {
- /*
- * Else still gathering constant name, so keep
- * copying into array.
- */
- if (varpos >= MAXCONST) {
- scripterror("constant name too long.\n");
- Cleanup(10);
- }
- varname[varpos++] = ch;
- }
- nextchar(ch);
- break;
- }
- }
- addchar(CHAR_NULL); /* Null terminate command string */
- compos = 0;
- comlen = strlen(buf);
- return (comlen);
- }
-
- /*
- * findmacro()
- * -----------
- * Searches the macro table for the specified macro. If found, returns
- * pointer to the macro definition, else returns -1.
- */
- int findmacro(name)
- char *name;
- {
- int i;
-
- for (i = 0; i < nummacros; i++)
- if (!strcmp(macros[i]->name, name))
- return (i);
- return (-1);
- }
-
- /*
- * getstring()
- * -----------
- * This function scans the command buffer starting at position
- * 'compos', and returns a pointer to the next identifier/string in
- * the buffer. The string is null-terminated, and if it was enclosed
- * in quotes, these are removed. compos is left pointing to just after
- * the string.
- */
- char *getstring()
- {
- char *s = combuf + compos;
- char *p, openquote;
-
- if (compos > comlen) {
- scripterror("missing parameter\n");
- Cleanup(10);
- }
- if (*s == CHAR_SPACE)
- s++;
-
- if (*s == CHAR_QUOTE || *s == CHAR_QUOTES) {
- openquote = *s++;
- for (p = s; *p && *p != openquote; p++) {
- if (*p == CHAR_ESC)
- p++; /* Skip over possible escaped quotes */
- }
- *p = CHAR_NULL;
- compos = (p - combuf) + 1;
- return (s);
- }
-
- for (p = s; *p && *p != CHAR_SPACE; p++)
- ;
- *p = CHAR_NULL;
- compos = (p - combuf) + 1;
- return (s);
- }
-
-
- /*
- * execline()
- * ----------
- * This function executes the current line in the command buffer,
- * handling macro expansion as necessary.
- */
- void execline()
- {
- char *cmd;
- char *p, *s;
- char *equals;
- MACRO *curmac;
- CONST *var;
- int macronum;
- char *ps[10];
- PARAM *par;
- int length;
- int i;
-
- chkabort();
- if (tracemode) {
- if (nestlevel > 0) {
- int i;
- for (i = 0; i < nestlevel; i++)
- print(" ");
- print2(combuf, "\n");
- } else {
- print3(itoa(linenum), ":", scriptname);
- print3(": ", combuf, "\n");
- }
- }
- cmd = getstring();
- for (i = 0; com[i].name && strcmp(cmd, com[i].name); i++)
- ;
- if (com[i].name) {
- /*
- * A valid command was found, so execute it
- */
- com[i].proc();
- } else if ((macronum = findmacro(cmd)) == -1) {
- /*
- * Not a macro -- is it a constant definition?
- */
- if (compos < comlen) {
- /*
- * There's a parameter after it; check is it an equals sign
- */
- equals = getstring();
- if (equals[0] != CHAR_EQUALS || equals[1] != CHAR_NULL) {
- /*
- * Wasn't an equals, so probably just a wrong command
- * with some superfluous parameters.
- */
- scripterror("unknown command ");
- print2(cmd, "\n");
- Cleanup(10);
- }
- /*
- * Okay, it's a constant definition. If it's already been
- * defined, print a warning but carry on anyway.
- */
- var = findconst(cmd);
- if (var) {
- scripterror("constant ");
- print2(cmd, " redefined.\n");
- } else
- var = addconst(cmd);
- /*
- * Now see was a value specified for the constant.
- * If it was, copy it into constant, else just
- * setup a null definition.
- */
- if (compos < comlen) {
- /*
- * Copy user's definition
- */
- cmd = getstring();
- if (strlen(cmd) >= MAXCONST) {
- scripterror("constant name too long.\n");
- Cleanup(10);
- }
- var->value = mymalloc(strlen(cmd)+1);
- strcpy(var->value, cmd);
- } else {
- /*
- * Setup null constant definition
- */
- var->value = mymalloc(1);
- strcpy(var->value, "");
- }
- } else {
- /*
- * Not a command, not a macro, not a constant definition.
- * Therefore, must be an error.
- */
- scripterror("unknown command ");
- print2(cmd, "\n");
- Cleanup(10);
- }
- } else {
- /*
- * It's a macro. Save a copy of the macro parameters into
- * $0 to $9, and then execute each line of the macro definition,
- * expanding parameter usages as necessary.
- */
- curmac = macros[macronum];
- length = 0;
- if (nestlevel >= MAXNEST) {
- scripterror("macros can only be nested ");
- print2(MAXNEST, " deep\n");
- Cleanup(10);
- }
- par = SafeAllocMem(PARAMSIZE + comlen + 1);
- params[nestlevel++] = par;
- par->size = comlen + 1;
-
- /*
- * Now initialise parameters $0 to $9
- */
- ps[0] = par->params;
- for (i = 1; i < 10; i++) {
- if (compos < comlen)
- /* Save parameter for later */
- ps[i] = par->params + (getstring() - combuf);
- else
- ps[i] = NULL;
- }
- memcpy(par->params, combuf, comlen+1);
-
- /*
- * Now recursively call execline() with command buffer setup
- * to be the current macro buffer line.
- */
- length = 0;
- s = curmac->text;
- for (length = 0; length < curmac->size;
- length += strlen(s) + 1, s = curmac->text + length) {
- /* Copy macro line into buffer, doing expansion */
- comlen = 0;
- for (p = s; *p; p++) {
- if (*p == '$') {
- p++;
- if (!*p) {
- scripterror("'$' must be followed by something\n");
- Cleanup(10);
- }
- if (isdigit(*p)) {
- int num = *p - '0';
-
- if (ps[num]) {
- /* Copy macro definition */
- if ((strlen(ps[num]) + comlen) >= MAXCOM - 10)
- goto endcommand; /* Eek! my first C Goto! */
- strcpy(combuf+comlen, ps[num]);
- comlen += strlen(ps[num]);
- }
- continue;
-
- }
- }
- combuf[comlen++] = *p;
- }
- endcommand:
- combuf[comlen] = CHAR_NULL;
- compos = 0;
- execline();
- }
- /*
- * Now free memory allocated for macro parameters
- */
- FreeMem(par, PARAMSIZE + par->size);
- nestlevel--;
- }
- }
-
-
- /*
- * execscript()
- * ------------
- * This function goes through all the commands in the script file,
- * executing them one by one. Each command is parsed, and then
- * the appropriate function called. Any errors that occur cause the
- * script to be aborted, so if this call returns, the script was
- * successfully executed.
- */
- void execscript()
- {
- while (readcommand(combuf, MAXCOM))
- execline();
- }
-
- /*
- * ====> Now the actual commands follow <====
- */
-
- /*
- * com_append()
- * ------------
- * This file opens the specified file for appending. If there was
- * already a file open, it is closed.
- */
-
- void com_append()
- {
- char *filename = getstring();
- BPTR lock;
-
- com_close();
-
- if (lock = Lock(filename, ACCESS_READ)) {
- UnLock(lock);
- outfile = Open(filename, MODE_OLDFILE);
- } else
- outfile = Open(filename, MODE_NEWFILE);
-
- if (!outfile) {
- scripterror("error appending to file ");
- print2(filename, "\n");
- Cleanup(10);
- }
- Seek(outfile, 0, OFFSET_END);
- toscreen = IsInteractive(outfile);
- }
-
- /*
- * com_open()
- * ----------
- * This command opens a file for output. The output of the ECHO,
- * FOREIGN and LIST commands goes to this file. If an output file
- * was already open, it is closed first.
- */
- void com_open()
- {
- char *filename = getstring();
-
- com_close();
- outfile = Open(filename, MODE_NEWFILE);
- if (!outfile) {
- scripterror("error opening file ");
- print2(filename, "\n");
- Cleanup(10);
- }
- toscreen = IsInteractive(outfile);
- }
-
- /*
- * com_close()
- * -----------
- * Closes the current output file, if any. All future output goes to
- * stdout.
- */
- void com_close()
- {
- if (outfile != Output()) {
- flushout();
- Close(outfile);
- outfile = Output();
- toscreen = IsInteractive(outfile);
- }
- }
-
- /*
- * com_config()
- * ------------
- * This command sets the name of the configuration file used by
- * CHECKFILES to get the names of the directories to scan from.
- */
- void com_config()
- {
- strcpy(configname, getstring());
- }
-
- /*
- * com_database()
- * --------------
- * This command sets the name of the database file read in
- * which contains the details of all the file headers.
- */
- void com_database()
- {
- strcpy(databasename, getstring());
- }
-
- /*
- * com_norequest()
- * ---------------
- * This command stops AmigaDos from putting up requesters when an
- * error occurs, or a volume isn't mounted.
- */
- void com_norequest()
- {
- struct Process *me = (struct Process *)FindTask(0L);
- me->pr_WindowPtr = (APTR)-1L;
- }
-
- /*
- * com_echo()
- * ----------
- * This command echoes the specified string to the output file.
- * A number of meta characters may be present in the string to be
- * output. See the documentation for more info. If any other parameter
- * is present on the line after the string, no newline is added
- * to the output, otherwise a newline is added automatically.
- *
- */
- void com_echo()
- {
- strcpy(line, getstring());
- if (compos >= comlen) /* Add NL if no second parameter */
- strcat(line, "\n");
- putstring(echoformat(out, MAXOUT, line));
- }
-
- /*
- * com_exec()
- * ----------
- * This command executes the indicated AmigaDOS command. The output
- * from the command goes into the current output file, if any.
- */
- void com_exec()
- {
- flushout();
- Execute(getstring(), NULL, outfile);
- }
-
- /*
- * com_format()
- * ------------
- * This command sets up the format string used by LIST and FOREIGN.
- * For details of what the format may contain, see the documentation.
- */
- void com_format()
- {
- strcpy(formatstring, getstring());
- if (compos >= comlen) /* No second parameter */
- strcat(formatstring, "\n");
- }
-
- /*
- * com_list()
- * ----------
- * This command is the biggy! It scans through the entire file
- * database, selecting records as specified with SELECT, and printing
- * them using the order specified with FORMAT. The order of printing
- * is as specified with SORT.
- */
- void com_list()
- {
- int i;
-
- CHECKDATABASE();
- curbytes = 0;
- curfiles = 0;
- for (i = 0; i < numrecs; i++) {
- if (ptrblock[i]->type == 0 && match(ptrblock[i], tree)) {
- format(out, MAXOUT, formatstring, ptrblock[i], checkfiles);
- putstring(out);
- curfiles++;
- curbytes += ptrblock[i]->length;
- }
- chkabort();
- }
- totalbytes += curbytes;
- totalfiles += curfiles;
- }
-
- /*
- * com_msg()
- * ---------
- * This command is identical to the ECHO command, except that
- * the output goes to the screen (i.e. stderr) instead of the
- * current output file. It is intended for printing informational
- * messages to let the user know of the program's progress, while
- * processing a large script.
- */
- void com_msg()
- {
- strcpy(line, getstring());
- if (compos >= comlen) /* Add NL if no second parameter */
- strcat(line, "\n");
- print(echoformat(out, MAXOUT, line));
- }
-
- /*
- * com_reset()
- * -----------
- * This command resets the running totals which are maintained,
- * which give the total number of bytes and total number of files
- * output so far.
- */
- void com_reset()
- {
- totalfiles = 0;
- totalbytes = 0;
- }
-
- /*
- * com_scan()
- * ----------
- * This command is identical to com_list(), except that no output is
- * produced. It is provided to allow counters to be updated, without
- * producing output, so that for example, the total number of files
- * in a listing can be output before the files themselves.
- */
-
- void com_scan()
- {
- int i;
-
- CHECKDATABASE();
- curbytes = 0;
- curfiles = 0;
- for (i = 0; i < numrecs; i++) {
- if (ptrblock[i]->type == 0 && match(ptrblock[i], tree)) {
- curfiles++;
- curbytes += ptrblock[i]->length;
- }
- chkabort();
- }
- totalbytes += curbytes;
- totalfiles += curfiles;
- }
-
- /*
- * com_macro()
- * -----------
- * This command lets you define a macro. The syntax is:
- *
- * MACRO name
- * ..
- * commands
- * ..
- * ENDM
- *
- * The macro can contain $1 to $9, which will be substituted at
- * run time by the appropriate parameters that were passed when
- * the macro was invoked.
- */
- void com_macro()
- {
- static char macroname[MACROLEN];
- int curline, curpos; /* Saves current line # and position in script */
- int length; /* Length of macro script */
- int macronum;
- MACRO *curmac;
- char *buf, *p;
-
- if (nummacros >= MAXMACRO) {
- scripterror("maximum of ");
- print2(itoa(MAXMACRO), " macros allowed\n");
- Cleanup(10);
- }
-
- curline = linenum;
- curpos = scriptpos;
-
- p = getstring();
- strncpy(macroname, p, MACROLEN-1);
- macroname[MACROLEN-1] = CHAR_NULL;
-
- /*
- * First of all, find out how much space the macro occupies
- */
- length = 0;
- while (readcommand(combuf, MAXCOM) && strcmp(combuf, "ENDM"))
- length += comlen + 1; /* The extra 1 is for the terminating \0 */
-
- /*
- * Now, restore linenumber and script position so we can read
- * in macro for real the second time.
- */
- linenum = curline;
- if (scriptpos >= scriptsize) {
- scripterror("missing ENDM in macro definition\n");
- Cleanup(10);
- }
-
- scriptpos = curpos;
-
- if ((macronum = findmacro(macroname)) == -1) /* New macro */
- macronum = nummacros++;
- else
- /* Macro redefinition, so release earlier definition */
- FreeMem(macros[macronum], macros[macronum]->size + MACROSIZE);
-
- curmac = SafeAllocMem(MACROSIZE + length);
- macros[macronum] = curmac;
- strcpy(curmac->name, macroname);
- curmac->size = length;
- buf = curmac->text;
-
- /*
- * Initialised macro definition, now copy macro text from script
- * file into macro buffer.
- */
- while (readcommand(combuf, MAXCOM) && strcmp("ENDM", combuf)) {
- strcpy(buf, combuf);
- buf += comlen + 1;
- }
- }
-
- /*
- * com_trace()
- * -----------
- * Turns on or off trace mode. When trace mode is on, every line that
- * is executed is displayed first. This is handy when executing
- * macros, to see exactly what is happening. Note that the -t option
- * on the command line will enable tracing for the whole file,
- * but if TRACE ON or OFF is encountered, this overrides -t.
- */
- void com_trace()
- {
- char *opt = getstring();
-
- if (!strcmp(opt, "ON"))
- tracemode = TRUE;
- else if (!strcmp(opt, "OFF"))
- tracemode = FALSE;
- else {
- scripterror("TRACE ON or TRACE OFF expected\n");
- Cleanup(10);
- }
- }
-
- /*
- * com_ignore()
- * ------------
- * This command takes a list of filenames. Each filename listed here
- * is remembered, and when CHECKFILES is done, any filename listed
- * here will be marked as valid, regardless of whether it really IS
- * valid or not. This is useful if you have some files online which
- * are updated by external programs (such as, for example, the output
- * from BBSINFO), and hence have "wandering" file sizes.
- */
- void com_ignore()
- {
- IGNORE *ig;
- char *p;
-
- while (compos < comlen) {
- ig = mymalloc(sizeof(IGNORE));
- p = getstring();
- if (strlen(p) >= CAT_LEN) {
- scripterror("filename too long.\n");
- Cleanup(10);
- }
- strcpy(ig->name, p);
- ig->next = firstignore;
- firstignore = ig;
- }
- }
-